﻿using System;
using System.ComponentModel;
using System.Reflection;
using Affinity.Core.Validation;

namespace Affinity.Flux
{
    /// <summary>
    /// Represents a property from which data flows, or an event from which execution flows.
    /// </summary>
    public class Outlet : IDisposable
    {
        /// <summary>
        /// Factory method, create an Outlet for the specified definition and component.
        /// </summary>
        /// <param name="outletDefinition"></param>
        /// <param name="component"></param>
        /// <returns></returns>
        public static Outlet Create(OutletDefinition outletDefinition, object component)
        {
            Assert.ArgumentNotNull(outletDefinition, "outletDefinition");
            return new Outlet(component, outletDefinition.Member, outletDefinition.Contract, outletDefinition.Flags);
        }

        /// <summary>
        /// Factory method, creates an Outlet for the specified component, member, contract, and flags.
        /// </summary>
        /// <param name="component"></param>
        /// <param name="member"></param>
        /// <param name="contract"></param>
        /// <param name="flags"></param>
        /// <returns></returns>
        public static Outlet Create(object component, MemberInfo member, Contract contract, OutletFlags flags)
        {
            return new Outlet(component, member, contract, flags);
        }

        /// <summary>
        /// Constructor accepting the component, member, contract, and flags.
        /// </summary>
        /// <param name="component"></param>
        /// <param name="member"></param>
        /// <param name="contract"></param>
        /// <param name="flags"></param>
        protected Outlet(object component, MemberInfo member, Contract contract, OutletFlags flags)
        {
            _Component = component;
            _Member = member;
            _Contract = contract;
            _Flags = flags;

            _Property = member as PropertyInfo;
            if (_Property != null)
            {
                _propertyName = _Property.Name;
                
                INotifyPropertyChanged notifyPropertyChanged = component as INotifyPropertyChanged;
                if (notifyPropertyChanged != null) notifyPropertyChanged.PropertyChanged += HandlePropertyChanged;
            }
        }

        /// <summary>
        /// Raised when the value of a property outlet changes.
        /// </summary>
        public event Action<object> PropertyChanged;

        /// <summary>
        /// Raises the PropertyChanged event.
        /// </summary>
        /// <param name="newValue"></param>
        private void RaisePropertyChanged(object newValue)
        {
            var propertyChanged = PropertyChanged;
            if (propertyChanged != null) propertyChanged(newValue);
        }

        /// <summary>
        /// Gets the component.
        /// </summary>
        public object Component
        {
            get { return _Component; }
        }

        /// <summary>
        /// Gets the contract.
        /// </summary>
        public Contract Contract
        {
            get { return _Contract; }
        }

        /// <summary>
        /// Gets the flags.
        /// </summary>
        public OutletFlags Flags
        {
            get { return _Flags; }
        }

        /// <summary>
        /// Gets the member corresponding to this outlet.
        /// </summary>
        public MemberInfo Member
        {
            get { return _Member; }
        }

        /// <summary>
        /// Disposes this object.
        /// </summary>
        public void Dispose()
        {
            if (_Component is INotifyPropertyChanged) ((INotifyPropertyChanged)_Component).PropertyChanged -= HandlePropertyChanged;
        }

        /// <summary>
        /// Gets the property value, for a property outlet.
        /// </summary>
        /// <returns></returns>
        public object GetPropertyValue()
        {
            PropertyInfo property = _Member as PropertyInfo;
            if (property == null) throw new Exception("Not a property outlet.");

            return property.GetValue(Component, null);
        }

        /// <summary>
        /// Returns a short description of this outlet.
        /// </summary>
        /// <returns></returns>
        public string ToShortString()
        {
            return string.Format("{0}.{1}", _Component.GetType().Name, _Member.Name);
        }

        /// <summary>
        /// Returns a description of this outlet.
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return string.Format("{0}.({1}) (Contract: {2}, Hash: {3})", _Component.GetType().Name, _Member, _Contract, GetHashCode());
        }

        /// <summary>
        /// Handles the component's PropertyChanged event.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void HandlePropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == _propertyName) RaisePropertyChanged(_Property.GetValue(Component, null));
        }

        private readonly Contract _Contract;
        private readonly object _Component;
        private readonly OutletFlags _Flags = OutletFlags.None;
        private readonly MemberInfo _Member;
        private readonly PropertyInfo _Property;
        private readonly string _propertyName;
    }
}
